package agents;

import jade.content.ContentElement;
import jade.content.lang.Codec.CodecException;
import jade.content.lang.sl.*;
import jade.content.onto.OntologyException;
import jade.content.onto.UngroundedException;
import jade.content.onto.basic.Action;
import jade.content.onto.basic.Result;
import jade.core.AID;
import jade.core.Agent;
import jade.core.Location;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.OneShotBehaviour;
import jade.core.behaviours.WakerBehaviour;
import jade.domain.FIPANames;
import jade.domain.JADEAgentManagement.QueryAgentsOnLocation;
import jade.domain.mobility.MobilityOntology;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;
import jade.util.leap.List;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;

import model.Profiler;
import model.Proposal;
import model.Tour;
import model.TourItem;
import behaviours.SubscribeAndListenBehaviour;
import behaviours.Subscriber;
import behaviours.TourNegotiationBehaviour;

import com.myprofile.profile.MuseumItem;
import com.myprofile.profile.ProfileType;
import com.myprofile.profile.MuseumItemDTO;

import daiia.ProfileManager;

public class ProfilerAgent extends Agent implements Subscriber, Profiler {

	private static final long serialVersionUID = -4471015679610377997L;
	
	private AID tourguide;
	private ProfileType profile;
	private String profilePath;
	private Location home;

	private ArrayList<Tour> tours;
	private ArrayList<String> tourIdsBought;

	// FOR DEBUGGING AND DEMONSTRATION PURPOSES
	private static boolean profileIsGreedy = model.Debug.greedy;
	
	private ArrayList<Proposal> finishedProposals;

	@SuppressWarnings("serial")
	@Override
	protected void setup() {
		
		finishedProposals = new ArrayList<Proposal>();

		// Register the SL content language
		getContentManager().registerLanguage(new SLCodec());

		// Register the mobility ontology
		getContentManager().registerOntology(MobilityOntology.getInstance());

		// References to home container
		home = here();

		// Load profile from xml
		if (profileIsGreedy)
			profilePath = "src/profiles/GreedyProfile.xml";

		else
			profilePath = "src/profiles/NormalProfile.xml";

		loadProfile(profilePath);
		System.out.println(this.getName() + ": Loaded profile: "
				+ profile.getAttitude());

		// Utility HashMap
		final HashMap<String, Integer> utilities = new HashMap<String, Integer>();
		utilities.put("P1", 30);
		utilities.put("P2", 45);
		utilities.put("P3", profile.getVisitedItems().getVisitedItem().size());
		utilities.put("T1", 30);
		utilities.put("T2", 75);
		utilities.put("T3", 90);

		// Proposal ArrayList
		final ArrayList<Proposal> proposals = generateProposals(profileIsGreedy);

		// Tour id ArrayList
		tourIdsBought = new ArrayList<String>();


		// Subscribe to services to get AID to tourguide
		String[] providers = { "tour-creation" };

		addBehaviour(new SubscribeAndListenBehaviour(this, providers));

		// Wait 2 seconds then run negotiation
		addBehaviour(new WakerBehaviour(this, 2000) {

			@Override
			protected void onWake() {
				addBehaviour(new TourNegotiationBehaviour(utilities, proposals,
						true, tourguide, myAgent, finishedProposals));
			}
		});
	}

	private ArrayList<Proposal> generateProposals(boolean profileIsGreedy2) {
		ArrayList<Proposal> proposals = new ArrayList<Proposal>();
		
		/*
		String[] buyItems1 = {"T1", "T3"};
		String[] sellItems1 = {"P1"};
		proposals.add(new Proposal(buyItems1, sellItems1, 0));
		
		String[] buyItems2 = {"T1", "T2"};
		String[] sellItems2 = {"P1"};
		proposals.add(new Proposal(buyItems2, sellItems2, 0));
		
		String[] buyItems3 = {"T1", "T2"};
		String[] sellItems3 = {"P2"};
		proposals.add(new Proposal(buyItems3, sellItems3, 0));
		
		String[] buyItems4 = {"T1", "T3"};
		String[] sellItems4 = {"P1", "P2"};
		proposals.add(new Proposal(buyItems4, sellItems4, 0));
		
		String[] buyItems5 = {"T3"};
		String[] sellItems5 = {"P1", "P3"};
		proposals.add(new Proposal(buyItems5, sellItems5, 0));
		
		String[] buyItems6 = {"T2"};
		String[] sellItems6 = {"P1", "P3"};
		proposals.add(new Proposal(buyItems6, sellItems6, 0));
		*/
		
		String[] buyItems7 = {"T2"};
		String[] sellItems7 = {"P2", "P3"};
		proposals.add(new Proposal(buyItems7, sellItems7, 0));
		
		// TODO Change order of proposals for greedy profiles
		
		if(!profileIsGreedy) {
			String[] buyItems8 = {"T2"};
			String[] sellItems8 = {"P1", "P2"};
			proposals.add(new Proposal(buyItems8, sellItems8, 0));
			
			String[] buyItems9 = {"T1"};
			String[] sellItems9 = {"P1"};
			proposals.add(new Proposal(buyItems9, sellItems9, 0));
		}
		
		else {
			String[] buyItems8 = {"T1"};
			String[] sellItems8 = {"P1"};
			proposals.add(new Proposal(buyItems8, sellItems8, 0));
			
			String[] buyItems9 = {"T2"};
			String[] sellItems9 = {"P1", "P2"};
			proposals.add(new Proposal(buyItems9, sellItems9, 0));
		}
		
		return proposals;
	}

	@Override
	protected void afterMove() {
		// Register the SL content language
		getContentManager().registerLanguage(new SLCodec());

		// Register the mobility ontology
		getContentManager().registerOntology(MobilityOntology.getInstance());

		if (here().toString().contains("Museum-Container")) {
			System.out.println(getName() + ": Im now at " + here());
			// Ask museum for touritem information
			this.addBehaviour(new TakeATourBehaviour());
		}

		if (here().equals(home)) {
			// Home again start another round
			System.out.println(getName() + ": Im now at " + here());

			if (profile.getVisitedItems().getVisitedItem().size() < 30) {

				HashMap<String, Integer> utilities = new HashMap<String, Integer>();
				utilities.put("P1", 30);
				utilities.put("P2", 45);
				utilities.put("P3", profile.getVisitedItems().getVisitedItem()
						.size() * 2);
				utilities.put("T1", 30);
				utilities.put("T2", 75);
				utilities.put("T3", 90);

				ArrayList<Proposal> proposals = new ArrayList<Proposal>();

				// TODO Smart algorithm that makes new proposals depending on
				// visisted tours
				// For now its manual
				String[] buyItems1 = {"T1", "T3"};
				String[] sellItems1 = {"P1"};
				int visistedItems = profile.getVisitedItems().getVisitedItem().size();
				proposals.add(new Proposal(buyItems1, sellItems1, visistedItems));
				
				String[] buyItems2 = {"T1", "T2"};
				String[] sellItems2 = {"P1"};
				proposals.add(new Proposal(buyItems2, sellItems2, visistedItems));
				
				String[] buyItems3 = {"T1", "T2"};
				String[] sellItems3 = {"P2"};
				proposals.add(new Proposal(buyItems3, sellItems3, visistedItems));
				
				String[] buyItems4 = {"T1", "T3"};
				String[] sellItems4 = {"P1", "P2"};
				proposals.add(new Proposal(buyItems4, sellItems4, visistedItems));
				
				String[] buyItems5 = {"T3"};
				String[] sellItems5 = {"P1", "P3"};
				proposals.add(new Proposal(buyItems5, sellItems5, visistedItems));
				
				String[] buyItems6 = {"T2"};
				String[] sellItems6 = {"P1", "P3"};
				proposals.add(new Proposal(buyItems6, sellItems6, visistedItems));
				
				String[] buyItems7 = {"T2"};
				String[] sellItems7 = {"P2", "P3"};
				proposals.add(new Proposal(buyItems7, sellItems7, visistedItems));
				
				String[] buyItems8 = {"T2"};
				String[] sellItems8 = {"P1", "P2"};
				proposals.add(new Proposal(buyItems8, sellItems8, visistedItems));
				
				String[] buyItems9 = {"T1"};
				String[] sellItems9 = {"P1"};
				proposals.add(new Proposal(buyItems8, sellItems8, visistedItems));
				
				String[] buyItems10 = {"T1"};
				String[] sellItems10 = {"P3"};
				proposals.add(new Proposal(buyItems10, sellItems10, visistedItems));

				System.out
						.println(getName()
								+ ": I have returned home and have tours left I can buy");
				System.out.println(getName()
						+ ": Initiating tour negotiation behaviour...");
				addBehaviour(new TourNegotiationBehaviour(utilities, proposals,
						true, tourguide, this, finishedProposals));
			} else {
				System.out.println(getName() + ": I have now visited "
						+ profile.getVisitedItems().getVisitedItem().size()
						+ " items at the museum. I cannot visit any more.");
			}
		}
	}

	@Override
	public void updateSubscription(String type, AID name) {
		if (type.equals("tour-creation")) {
			System.out.println(getName() + ": Found Tour Guide Agent.");
			tourguide = name;
		}
	}

	@Override
	public ProfileType getProfile() {
		return this.profile;
	}

	private void loadProfile(String path) {
		ProfileManager pm = new ProfileManager();
		profile = pm.loadProfile(path);
	}

	@Override
	public void storeTour(ArrayList<Tour> tours) {
		for (Tour t : tours)
			tourIdsBought.add(t.getTourId());
		this.tours = tours;
		String visited = "";
		for (String s : tourIdsBought)
			visited = visited + " " + s;
		System.out.println(getName() + ": My bought tours now contains: "
				+ visited);
	}

	private class TakeATourBehaviour extends OneShotBehaviour {

		@Override
		public void action() {
			// ListenToAMSRespone (done when we found our agents and sent a
			// message to them)
			myAgent.addBehaviour(new ListenToAMSResponseBehaviour());

			// ListenToCuratorResponse (done when we move out of the container)
			myAgent.addBehaviour(new ListenToCuratorBehaviour());

			// Request to find all agents in our container
			myAgent.addBehaviour(new FindAgentsInContainerBehaviour());

		}

		private class FindAgentsInContainerBehaviour extends OneShotBehaviour {

			@Override
			public void action() {
				QueryAgentsOnLocation qal = new QueryAgentsOnLocation();
				qal.setLocation(here());
				Action action = new Action(myAgent.getAID(), qal);

				ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
				msg.addReceiver(myAgent.getAMS());
				msg.setProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST);
				msg.setLanguage(FIPANames.ContentLanguage.FIPA_SL);
				msg.setOntology(MobilityOntology.getInstance().getName());

				try {
					myAgent.getContentManager().fillContent(msg, action);
				} catch (CodecException e) {
					e.printStackTrace();
				} catch (OntologyException e) {
					e.printStackTrace();
				}
				System.out
						.println(myAgent.getName()
								+ ": Querying my current container for existing agents...");
				myAgent.send(msg);
			}
		}

		private class ListenToAMSResponseBehaviour extends Behaviour {

			private boolean done = false;

			@Override
			public void action() {
				ACLMessage receivedMessage = myAgent.receive(MessageTemplate
						.MatchSender(myAgent.getAMS()));

				if (receivedMessage != null) {
					ContentElement content = null;
					try {
						content = myAgent.getContentManager().extractContent(
								receivedMessage);
					} catch (UngroundedException e) {
						e.printStackTrace();
					} catch (CodecException e) {
						e.printStackTrace();
					} catch (OntologyException e) {
						e.printStackTrace();
					}

					System.out.println(myAgent.getName()
							+ ": Received a response from the AMS!");

					// The code below sends a request for curators to all agents
					// in our container
					ACLMessage msg = new ACLMessage(ACLMessage.PROPOSE);
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST);

					Result result = (Result) content;
					List listOfAgents = (List) result.getValue();
					@SuppressWarnings("rawtypes")
					Iterator it = listOfAgents.iterator();
					while (it.hasNext()) {
						AID aid = (AID) it.next();
						msg.addReceiver(aid);
					}

					myAgent.send(msg);

					System.out
							.println(myAgent.getName()
									+ ": Sent a request for curators to all agents in my container");

					done = true;
				} else
					block();
			}

			@Override
			public boolean done() {
				return done;
			}
		}

		private class ListenToCuratorBehaviour extends Behaviour {

			@Override
			public void action() {
				ACLMessage msg = myAgent
						.receive(MessageTemplate
								.MatchProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST));

				if (msg != null) {

					// This means the curator has checked the inventory for our
					// item
					if (msg.getPerformative() == ACLMessage.INFORM) {
						try {
							MuseumItemDTO item = (MuseumItemDTO) msg
									.getContentObject();

							// Visit item
							visitItem(item);

							// Are we done here now?
							if (!itemsLeftToVisit()) {
								System.out
										.println(myAgent.getName()
												+ ": No items left to visit. Returning home...");
								doMove(home);
							}
						}
						// Should not happen
						catch (UnreadableException e) {
							e.printStackTrace();
						}
					}

					// We just sent a message asking all agents in our container
					// If they were a curator. If we got here this means that we
					// have found one.
					if (msg.getPerformative() == ACLMessage.CONFIRM) {
						// Send a request for every item in our tour
						// How many tours do we have?
						for (Tour tour : tours) {
							for (TourItem item : tour.getTouritems()) {
								myAgent.addBehaviour(new RequestItemBehaviour(
										item, msg.getSender()));
							}
						}
					}

				} else
					block();

			}

			private boolean itemsLeftToVisit() {

				// For every item in our list of tours
				// Check if it has been visited
				// If we find an item that has not been visited
				// return true
				for (Tour tour : tours)
					for (TourItem touritem : tour.getTouritems())
						if (!touritem.isVisited())
							return true;

				// If we got this far, we have no items left to visit
				// Clear the tour
				tours.clear();
				return false;
			}

			private void visitItem(MuseumItemDTO item) {
				System.out.println(myAgent.getName() + ": Visited an item: "
						+ item.getName());

				MuseumItem visitedItem = new MuseumItem();
				visitedItem.setId(item.getId());
				visitedItem.setName(item.getName());

				// Calculate rating for this item
				int rating = rateItem(item);
				visitedItem.setRating(rating);
				System.out.println(myAgent.getName()
						+ ": Gave it a rating of: " + visitedItem.getRating());

				// Add the item to the visitedItems list for this profile
				profile.getVisitedItems().getVisitedItem().add(visitedItem);
				ProfileManager pm = new ProfileManager();

				// Find the corresponding tour item and change the visited tag
				// for this tour item
				tagItemAsVisited(item);

				// TODO Remove this comment!
				// pm.dumpProfile(profile, profilePath);
			}

			private void tagItemAsVisited(MuseumItemDTO item) {
				// Find the item
				for (Tour tour : tours) {
					for (TourItem touritem : tour.getTouritems()) {
						if (touritem.getId().equals(item.getId())) {
							touritem.setVisited(true);
							return;
						}
					}
				}
			}

			private int rateItem(MuseumItemDTO item) {
				int rating = 1;
				StringTokenizer subjects = new StringTokenizer(
						item.getSubject(), ",");
				java.util.List<String> interests = profile.getInterests()
						.getInterest();

				for (String interest : interests)
					while (subjects.hasMoreTokens())
						if (subjects.nextToken().trim().contains(interest))
							rating += 2;
				return rating > 5 ? 5 : rating;
			}

			@Override
			public boolean done() {
				// We are done with this behaviour when we have moved back home
				return here().equals(home);
			}

			private class RequestItemBehaviour extends OneShotBehaviour {

				TourItem item;
				AID curator;

				public RequestItemBehaviour(TourItem item, AID curator) {
					this.item = item;
					this.curator = curator;
				}

				@Override
				public void action() {
					ACLMessage msg = new ACLMessage(ACLMessage.REQUEST);
					msg.setProtocol(FIPANames.InteractionProtocol.FIPA_REQUEST);
					msg.addReceiver(curator);
					msg.setContent(item.getId());
					myAgent.send(msg);
				}

			}
		}
	}
}
